/// **BoundaryDetector**
///
/// 用于文本编辑器中“光标双击选词 / 三击选行”等操作的边界判定工具类。
///
/// ### 特性
/// - 单词字符：`[A-Za-z0-9_]`；
/// - 如果点在非单词字符（如 `+`, `=`，或汉字），则选中一段
///   **“非空白的连续片段”**（但不会跨行）；
/// - 行边界基于 `\n` 判断；
///
/// 常见用法：
/// ```dart
/// final detector = BoundaryDetector();
/// final (start, end) = detector.wordBoundsAt("foo = bar;", 6);
/// print("word: ${text.substring(start, end)}"); // 输出 "bar"
/// ```
class BoundaryDetector {
  const BoundaryDetector();

  /// 判断一个字符是否为 ASCII 单词字符 `[A-Za-z0-9_]`。
  bool isAsciiWordChar(String ch) {
    if (ch.isEmpty) return false;
    final code = ch.codeUnitAt(0);
    return (code >= 48 && code <= 57) || // 0-9
        (code >= 65 && code <= 90) ||    // A-Z
        (code >= 97 && code <= 122) ||   // a-z
        ch == '_';
  }

  /// 返回 [offset] 所在行的起始索引（含）。
  ///
  /// - 会向左回溯直到遇到 `\n` 或到达文本开头；
  /// - 若 [offset] 超出范围，会自动 clamp 到 [0, text.length]。
  int lineStartOf(String text, int offset) {
    offset = offset.clamp(0, text.length);
    while (offset > 0 && text.codeUnitAt(offset - 1) != 0x0A) {
      offset--;
    }
    return offset;
  }

  /// 返回 [offset] 所在行的结束索引（不含）。
  ///
  /// - 会向右扫描直到遇到 `\n` 或到达文本结尾；
  /// - 若 [offset] 超出范围，会自动 clamp 到 [0, text.length]。
  int lineEndOf(String text, int offset) {
    offset = offset.clamp(0, text.length);
    while (offset < text.length && text.codeUnitAt(offset) != 0x0A) {
      offset++;
    }
    return offset;
  }

  /// 返回光标处“单词/片段”的 `[start, end)` 边界。
  ///
  /// 规则：
  /// - 若命中 `[A-Za-z0-9_]`，则扩展到左右所有连续单词字符；
  /// - 否则，扩展为一段 **非空白（非空格/Tab/换行）连续片段**，但不会跨行；
  /// - 若光标恰好在两个字符之间，优先看左侧字符。
  ///
  /// 常见于“双击选词”。
  (int, int) wordBoundsAt(String text, int offset) {
    offset = offset.clamp(0, text.length);
    if (text.isEmpty) return (0, 0);

    final int lineStart = lineStartOf(text, offset);
    final int lineEnd = lineEndOf(text, offset);
    int left = offset > 0 ? offset - 1 : 0;
    int right = offset;

    String cur = offset < text.length ? text.substring(offset, offset + 1) : '';
    final bool isWord = cur.isNotEmpty && isAsciiWordChar(cur);

    // 若刚好点在两个字符之间，优先看左侧字符
    if (cur.isEmpty && offset > 0) {
      final leftChar = text.substring(offset - 1, offset);
      if (isAsciiWordChar(leftChar)) {
        left = offset - 1;
        right = offset;
        cur = leftChar;
      }
    }

    if (isWord) {
      // 扩展到连续单词字符
      while (left > lineStart &&
          isAsciiWordChar(text.substring(left - 1, left))) {
        left--;
      }
      while (right < lineEnd &&
          isAsciiWordChar(text.substring(right, right + 1))) {
        right++;
      }
    } else {
      // 非单词：扩展为一段“非空白连续区”（不跨行）
      bool isNonSpace(String c) => c.trim().isNotEmpty && c != '\n';
      while (left > lineStart && isNonSpace(text.substring(left - 1, left))) {
        left--;
      }
      while (right < lineEnd && isNonSpace(text.substring(right, right + 1))) {
        right++;
      }
    }
    return (left, right);
  }

  /// 返回 offset 所在行的 `[start, end)` 边界。
  ///
  /// 常见于“三击选中整行”。
  (int, int) lineBoundsAt(String text, int offset) {
    final s = lineStartOf(text, offset);
    final e = lineEndOf(text, offset);
    return (s, e);
  }
}
